package com.legendframework.preposed;

import com.legendframework.core.AbstractLegendPlugin;
import com.legendframework.core.LegendPlugin;
import com.legendframework.core.dao.Return;
import com.legendframework.core.ioc.BeansFactoryManager;
import com.legendframework.core.ioc.scanner.ClassScanner;
import org.bukkit.Bukkit;
import org.bukkit.plugin.*;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * @author 196
 *
 * 这是插件开发框架开发的会被Bukkit所加载的插件，其他由本preposed版开发的插件
 * 不会被Bukkit加载，只会被legendframework-preposed加载
 */
public class Legendframework extends LegendPlugin {

    public Legendframework(){
        super();
    }

    public void start() {
        Bukkit.getScheduler().runTask(this,() -> {
            //获取附属目录
            File pluginsFile = new File(getPluginsDirectoryPath());
            //第一次加载
            if (!pluginsFile.exists()) {
                //创建插件配置文件目录
                pluginsFile.mkdirs();
            }else {
                BeansFactoryManager factoryManager = this.getStore().getBeansFactoryManager();

                //ioc选取规则
                Predicate<Class> predicate = cls -> factoryManager.getBeansGenerates()
                        .stream()
                        .anyMatch(item -> cls.isAnnotationPresent(item.getAnnotation()));

                //插件集合
                List<Return<AbstractLegendPlugin,Set<Class<?>>>> pluginClasses = new ArrayList<>();

                Field isEnabled;
                try {
                    isEnabled = JavaPlugin.class.getDeclaredField("isEnabled");
                } catch (NoSuchFieldException e) {
                    throw new RuntimeException(e);
                }

                //附属特定的一组操作
                Consumer<File> consumer = (file) -> {
                    try {
                        Plugin plugin = Bukkit.getPluginManager().loadPlugin(file);
                        if (!(plugin instanceof JavaPlugin)) {
                            return;
                        }
                        if (plugin instanceof LegendSubsidiaryPlugin){
                            Field isUse = LegendSubsidiaryPlugin.class.getDeclaredField("isUse");
                            isUse.setAccessible(true);
                            isUse.set(plugin,true);
                            Field store = AbstractLegendPlugin.class.getDeclaredField("store");
                            store.setAccessible(true);
                            store.set(plugin,getStore());
                        }
                        //包扫描工具类
                        if (plugin instanceof AbstractLegendPlugin) {
                            Return<AbstractLegendPlugin, Set<Class<?>>> build = Return.build((AbstractLegendPlugin) plugin, new ClassScanner("", true, null, predicate, file.toURL(),this.getClassLoader()).doScanAllClasses());
                            pluginClasses.add(build);
                        }
                        plugin.onLoad();
                        //临时启用这个插件
                        isEnabled.setAccessible(true);
                        isEnabled.set(plugin,true);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                };

                //按照依赖进行加载
                Map<String, File> plugins = new HashMap<String, File>();
                Set<String> loadedPlugins = Arrays.stream(Bukkit.getPluginManager().getPlugins()).filter(Plugin::isEnabled).map(Plugin::getName).collect(Collectors.toSet());
                Map<String, Collection<String>> dependencies = new HashMap<String, Collection<String>>();
                Map<String, Collection<String>> softDependencies = new HashMap<String, Collection<String>>();

                for (File file : getPluginFiles()) {
                    PluginDescriptionFile description;
                    try (JarFile jarFile = new JarFile(file);
                         InputStream inputStream = jarFile.getInputStream(new JarEntry("plugin.yml"))){
                        description = new PluginDescriptionFile(inputStream);
                    } catch (InvalidDescriptionException | IOException e) {
                        log.error("Could not load '" + file.getPath() + "' in folder '" + file.getPath() + "'", e);
                        continue;
                    }

                    plugins.put(description.getName(), file);

                    Collection<String> softDependencySet = description.getSoftDepend();
                    if (softDependencySet != null && !softDependencySet.isEmpty()) {
                        if (softDependencies.containsKey(description.getName())) {
                            // Duplicates do not matter, they will be removed together if applicable
                            softDependencies.get(description.getName()).addAll(softDependencySet);
                        } else {
                            softDependencies.put(description.getName(), new LinkedList<String>(softDependencySet));
                        }
                    }

                    Collection<String> dependencySet = description.getDepend();
                    if (dependencySet != null && !dependencySet.isEmpty()) {
                        dependencies.put(description.getName(), new LinkedList<String>(dependencySet));
                    }

                    Collection<String> loadBeforeSet = description.getLoadBefore();
                    if (loadBeforeSet != null && !loadBeforeSet.isEmpty()) {
                        for (String loadBeforeTarget : loadBeforeSet) {
                            if (softDependencies.containsKey(loadBeforeTarget)) {
                                softDependencies.get(loadBeforeTarget).add(description.getName());
                            } else {
                                // softDependencies is never iterated, so 'ghost' plugins aren't an issue
                                Collection<String> shortSoftDependency = new LinkedList<String>();
                                shortSoftDependency.add(description.getName());
                                softDependencies.put(loadBeforeTarget, shortSoftDependency);
                            }
                        }
                    }
                }

                while (!plugins.isEmpty()) {
                    boolean missingDependency = true;
                    Iterator<Map.Entry<String, File>> pluginIterator = plugins.entrySet().iterator();

                    while (pluginIterator.hasNext()) {
                        Map.Entry<String, File> entry = pluginIterator.next();
                        String plugin = entry.getKey();

                        if (dependencies.containsKey(plugin)) {
                            Iterator<String> dependencyIterator = dependencies.get(plugin).iterator();

                            while (dependencyIterator.hasNext()) {
                                String dependency = dependencyIterator.next();

                                // Dependency loaded
                                if (loadedPlugins.contains(dependency)) {
                                    dependencyIterator.remove();

                                    // We have a dependency not found
                                } else if (!plugins.containsKey(dependency)) {
                                    missingDependency = false;
                                    pluginIterator.remove();
                                    softDependencies.remove(plugin);
                                    dependencies.remove(plugin);

                                    log.error(
                                            "Could not load '" + entry.getValue().getPath() + "' in folder '" + pluginsFile.getPath() + "'",
                                            new UnknownDependencyException(dependency));
                                    break;
                                }
                            }

                            if (dependencies.containsKey(plugin) && dependencies.get(plugin).isEmpty()) {
                                dependencies.remove(plugin);
                            }
                        }
                        if (softDependencies.containsKey(plugin)) {
                            Iterator<String> softDependencyIterator = softDependencies.get(plugin).iterator();

                            while (softDependencyIterator.hasNext()) {
                                String softDependency = softDependencyIterator.next();

                                // Soft depend is no longer around
                                if (!plugins.containsKey(softDependency)) {
                                    softDependencyIterator.remove();
                                }
                            }

                            if (softDependencies.get(plugin).isEmpty()) {
                                softDependencies.remove(plugin);
                            }
                        }
                        if (!(dependencies.containsKey(plugin) || softDependencies.containsKey(plugin)) && plugins.containsKey(plugin)) {
                            // We're clear to load, no more soft or hard dependencies left
                            File file = plugins.get(plugin);
                            pluginIterator.remove();
                            missingDependency = false;

                            consumer.accept(file);
                            loadedPlugins.add(plugin);
                            continue;
                        }
                    }

                    if (missingDependency) {
                        // We now iterate over plugins until something loads
                        // This loop will ignore soft dependencies
                        pluginIterator = plugins.entrySet().iterator();

                        while (pluginIterator.hasNext()) {
                            Map.Entry<String, File> entry = pluginIterator.next();
                            String plugin = entry.getKey();

                            if (!dependencies.containsKey(plugin)) {
                                softDependencies.remove(plugin);
                                missingDependency = false;
                                File file = entry.getValue();
                                pluginIterator.remove();

                                consumer.accept(file);
                                loadedPlugins.add(plugin);
                            }
                        }
                        // We have no plugins left without a depend
                        if (missingDependency) {
                            softDependencies.clear();
                            dependencies.clear();
                            Iterator<File> failedPluginIterator = plugins.values().iterator();

                            while (failedPluginIterator.hasNext()) {
                                File file = failedPluginIterator.next();
                                failedPluginIterator.remove();
                                log.error( "Could not load '" + file.getPath() + "' in folder '" + pluginsFile.getPath() + "': circular dependency detected");
                            }
                        }
                    }
                }

                pluginClasses.forEach(p -> {
                    p.getR2().forEach(item -> factoryManager.addPool(item,p.getR1()));
                });

                pluginClasses.forEach(p -> {
                    p.getR2().forEach(item -> factoryManager.generate(item,p.getR1()));
                });

                //等待待创建池销毁完毕后再向下执行
                while (getStore().getBeansFactory().getCreatedBeans().size() > 0) {
                    if (getStore().getBeansFactory().getCreatedBeans().size() == 0) {
                        break;
                    }
                }

                //加载插件
                pluginClasses.forEach(p -> {
                    try {
                        isEnabled.setAccessible(true);
                        isEnabled.set(p.getR1(),false);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                    Bukkit.getPluginManager().enablePlugin(p.getR1());
                });
            }
            pluginsLoadEndHandler();
        });
    }

    public void end() {

    }

    /**
     * 附属插件加载完毕执行器
     */
    protected void pluginsLoadEndHandler(){}

    public String getRootCmd() {
        return null;
    }

    /**
     * 获取附属插件目录绝对路径
     * @return
     */
    protected String getPluginsDirectoryPath(){
        return this.getDataFolder().getParentFile().getAbsolutePath() + "/" + this.getName() + "/" + "plugins";
    }

    /**
     * 获取插件附属目录中的所以附属插件
     * @return
     */
    protected List<File> getPluginFiles(){
        File pluginsFile = new File(getPluginsDirectoryPath());
        File[] files = pluginsFile.listFiles();
        if (files == null) {
            return new ArrayList<>();
        }
        return Arrays.stream(files).filter(file -> file.getName().endsWith(".jar")).collect(Collectors.toList());
    }

    @Override
    public void reload() {

    }
}
